﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NUnitResults
{
    /// <summary>
    /// Composite ITestResult.
    /// </summary>
    [System.Diagnostics.DebuggerDisplay("TestSuite(Name = {Name}, Status = {Status})")]
    public class TestSuite : ITestResult
    {
        #region Properties

        /// <summary>
        /// Gets this result's sub-results.
        /// </summary>
        public IList<ITestResult> InnerCases { get; private set; }

        /// <summary>
        /// Gets the full name of this test case.
        /// </summary>
        public String FullName
        {
            get
            {
                if (String.IsNullOrEmpty(Namespace))
                {
                    return Name;
                }
                if (String.IsNullOrEmpty(Name))
                {
                    return Namespace;
                }

                return String.Format("{0}.{1}", Namespace, Name);
            }
        }

        /// <summary>
        /// Gets the namespace of this test suite.
        /// </summary>
        public String Namespace { get; private set; }

        /// <summary>
        /// Gets the status of this test result.
        /// </summary>
        public Status Status { get; private set; }

        /// <summary>
        /// Gets the name of this test suite.
        /// </summary>
        public String Name { get; private set; }

        /// <summary>
        /// Gets the type of this suite.
        /// </summary>
        public SuiteType SuiteType { get; private set; }

        /// <summary>
        /// Gets the number of tests in this test suite.
        /// </summary>
        public TestCaseCount TestCaseCount { get; private set; }

        /// <summary>
        /// Gets the time that this suite took to run, if it ran.
        /// </summary>
        public Nullable<TimeSpan> Time { get; private set; }

        /// <summary>
        /// Gets the number of asserts in this test suite.
        /// </summary>
        public Nullable<Int32> Asserts { get; private set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Creates a new TestSuite with the supplied name and sub-results.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="innerCases"></param>
        public TestSuite(String name, String nspace, Status status, SuiteType suiteType, Nullable<TimeSpan> time, Nullable<Int32> asserts, IList<ITestResult> innerCases)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }
            if (nspace == null)
            {
                throw new ArgumentNullException("nspace");
            }
            if (innerCases == null)
            {
                throw new ArgumentNullException("innerCases");
            }

            Name = name;
            Namespace = nspace;
            Status = status;
            SuiteType = suiteType;
            Time = time;
            Asserts = asserts;
            InnerCases = innerCases;
            TestCaseCount = TestCaseCount.FromCases(innerCases);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Applies the supplied action to this instance and all sub-results.
        /// </summary>
        /// <param name="result"></param>
        /// <param name="action"></param>
        public void Iterate(Action<ITestResult> action)
        {
            if (action == null)
            {
                throw new ArgumentNullException("action");
            }

            action(this);

            foreach (var r in InnerCases)
            {
                r.Iterate(action);
            }
        }

        #endregion

        #region Object Overrides

        /// <summary>
        /// Converts this test suite to a string.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return String.Format("TestSuite(\"{0}\", \"{1}\", \"{2}\", \"{3}\", \"{4}\", \"{5}\")", 
                                 Name,
                                 Namespace,
                                 Status,
                                 SuiteType,
                                 Time.HasValue ? Time.ToString() : "[null]",
                                 Asserts.HasValue ? Asserts.ToString() : "[null]") + Environment.NewLine +
                String.Join(Environment.NewLine, InnerCases.Select(c => c.ToString()).ToArray());
        }

        /// <summary>
        /// Tests this test suite for equality with another object.
        /// NOTE: This operation is quite costly, and is provided mainly
        /// for unit testing.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            var other = obj as TestSuite;

            if (other == null)
            {
                return false;
            }

            if (this.Name != other.Name)
            {
                return false;
            }

            if (this.Namespace != other.Namespace)
            {
                return false;
            }

            if (this.Status != other.Status)
            {
                return false;
            }

            if (this.SuiteType != other.SuiteType)
            {
                return false;
            }

            if (this.Time != other.Time)
            {
                return false;
            }

            if (this.Asserts != other.Asserts)
            {
                return false;
            }

            if (this.InnerCases.Count != other.InnerCases.Count)
            {
                return false;
            }

            for (int i = 0; i < this.InnerCases.Count; i++)
            {
                if (!this.InnerCases[i].Equals(other.InnerCases[i]))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Provides a hashcode for this object. Again,
        /// this operation is quite costly for instances
        /// of this class due to its recursive nature.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            var hashcode = 
                Name.GetHashCode() ^ 
                Namespace.GetHashCode() ^ 
                SuiteType.GetHashCode() ^ 
                Status.GetHashCode() ^ 
                Time.GetHashCode() ^ 
                Asserts.GetHashCode();

            foreach (var inner in InnerCases)
            {
                hashcode ^= inner.GetHashCode();
            }

            return hashcode;
        }

        #endregion
    }
}
